Fork me on GitHub

Egg 源码分析之 egg-core(一)

转载自掘金网络,原文链接:https://juejin.im/post/5cf47b3df265da1bac40007e

10 阿里监控

Node.js 性能平台(alinode)

是面向所有 Node.js 应用提供 性能监控、安全提醒、故障排查、性能优化 等服务的整体性解决方案,提供完善的工具链和服务,协助开发者快速发现和定位线上问题。

1
npm i nodeinstall -g

提供了egg-alinode 来快速接入,无需安装 agenthub 等额外的常驻服务。

1
2
3
4
5
6
7
8
npm i egg-alinode --save
复制代码
// /config/plugin.js

exports.alinode = {
enable:true,
package:'egg-alinode',
}

申请一下服务

访问控制台

控制台地址:node.console.aliyun.com

image.png

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
// config/config.default.js
exports.alinode = {
enable: true,
appid: '***', // Node.js 性能平台给您的项目生成的 appid
secret: '***', // Node.js 性能平台给您的项目生成的 secret
logdir: '***', //可选,Node.js 性能平台日志输出地址绝对路径,与 NODE_LOG_DIR 保持一致。如:/tmp/,也可以不写
error_log: [
// '您的应用在业务层面产生的异常日志的路径,数组,可选,可配置多个',
// '例如:/root/.logs/error.#YYYY#-#MM#-#DD#.log',
// '不更改 Egg 默认日志输出路径可不配置本项目',
],// 可选
agentidMode:'IP', // 可选,如果设置,则在实例ID中添加部分IP信息,用于多个实例 hostname 相同的场景(以容器为主)
};

然后你就能愉快针对你的egg,进行监控了

image.png

获取swgger地址 输入浏览器

你看到就是文档了

image.png

点击try it out

image.png

输入你传的值,然后点击Execute

image.png

结果

image.png

你就可以获取到接口传递过来的值,效果类似postman,但是清晰程度比postman好

12.5 常见问题

一般情况下都不会有问题,但是如果你这时候巧妙的用了egg-static,那么你就会报错了
经过排查,你就会发现

/node_modules/egg-swagger2/app.js

image.png

它会是一个数组,然后报错必须是个字符串,然后你懂得..你给他做成一个字符串即可

11 引入静态文件

11.1 经过测试插件设置

1
2
3
4
exports.ejs = {
enable: true,
package: 'egg-view-ejs',
};

11.2 配置设置

a:静态文件

1
2
3
4
5
6
7
config.static = {

prefix: '/',

dir: path.join(appInfo.baseDir, 'app/public/')

}

当然此时你会遇到一个问题,你想要多个文件该如何事好

1
2
3
4
5
6
config.static = {
prefix: '/',
dir: [ path.join(appInfo.baseDir, 'app/view/'),
path.join(appInfo.baseDir, 'app/public/uploads/'),
path.join(appInfo.baseDir, 'app/public/swagger/') ],
};

b:模板设置

1
2
3
4
5
6
7
config.view = {
defaultExt: '.html',
mapping: {
'.ejs': 'ejs',
'.html': 'ejs',
}
}

11.3 路由控制器设置

//将 index.html 放在app/view里,静态文件放在public里

1
2
3
4
5
const { ctx } = this;

// render user.html

yield ctx.render('index');

12 egg-swagger2

12.1 运营场景

作为后台,例如有人需要后台提供文档….人家java都有swagger,egg在 egg-swagger2 支持下,我们也可以使用。

12.2 安装

npm i egg-swagger2 -S

12.3 开启插件

1
2
3
4
5
// config/plugin.js
exports.swagger2 = {
enable: true,
package: 'egg-swagger2',
};

12.4 插件配置

config.default.js 中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
config.swagger2 = {
enable: true, // 禁用swagger , 默认为true
base: {
/* default config,support cover
schemes: [
'http',
],
host: '127.0.0.1:7001',
basePath: '/',
consumes: [
'application/json',
],
produces: [
'application/json',
],
*/
info: {
description: '文档介绍,
version: '1.0.0',
title: '文档名称',
contact: {
email: 'caandoll@aliyun.com',
},
license: {
name: 'Apache 2.0',
url: 'http://www.apache.org/licenses/LICENSE-2.0.html',
},
},
tags: [{
name: 'admin',
description: 'Admin desc',
},
{
name: 'role',
description: 'Role desc',
},
],
definitions: {
// model definitions
},
securityDefinitions: {
// security definitions
}
},
};

12.4 例子

在 /app/router.js文件中

12.4.1 post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
module.exports = app => {
const { router, controller, swagger } = app;
router.post('/login', controller.test.postLogin);
swagger.post('/login', {
tags: [
'admin',
],
summary: 'Login a admin',
description: '',
parameters: [
{
in: 'body',
name: 'body',
description: 'admin\'s username & password',
required: true,
schema: {
type: 'object',
required: [ 'username', 'password' ],
properties: {
username: {
type: 'string',
description: 'admin\'s username',
},
password: {
type: 'string',
description: 'admin\'s password',
},
},
},
},
],
responses: {
200: {
description: 'SUCCEED',
schema: {
type: 'object',
properties: {
status: {
type: 'string',
description: 'status',
},
data: {
type: 'object',
description: 'data',
properties: {
token: {
type: 'string',
description: 'token',
},
},
},
},
},
},
},
});
}

12.4.2 get请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
module.exports = app => {
const { router, controller, swagger } = app;
router.get('/roles', controller.test.getRoles);
swagger.get('/roles', {
tags: ['role',],
summary: 'search role by page',
description: '',
parameters: [{
in: 'query',
name: 'name',
description: 'role\'s name',
},
{
in: 'query',
name: 'pageIndex',
description: 'pageIndex',
},
{
in: 'query',
name: 'pageSize',
description: 'pageSize',
},
],
responses: {
200: {
description: 'SUCCEED',
schema: {
type: 'object',
properties: {
status: {
type: 'string',
description: 'status',
},
datas: {
type: 'array',
description: 'result datas',
properties: {
token: {
type: 'string',
description: 'token',
},
},
},
pageIndex: {
type: 'number',
description: 'pageIndex',
},
pageSize: {
type: 'number',
description: 'pageSize',
},
totalCount: {
type: 'number',
description: 'totalCount',
},
},
},
},
},
});
}

12.4.3 swagger的使用

npm run dev 跑起来

![image.png](data:image/svg+xml;utf8,)

获取swgger地址 输入浏览器

你看到就是文档了

image.png

点击try it out

image.png

输入你传的值,然后点击Execute

image.png

结果

image.png

你就可以获取到接口传递过来的值,效果类似postman,但是清晰程度比postman好

12.5 常见问题

一般情况下都不会有问题,但是如果你这时候巧妙的用了egg-static,那么你就会报错了
经过排查,你就会发现

/node_modules/egg-swagger2/app.js

image.png

它会是一个数组,然后报错必须是个字符串,然后你懂得..你给他做成一个字符串即可

13 表单校验机制

npm egg-validate-plus –save

13.1 开启插件

1
2
3
4
5
6
// config/plugin.{env}.js

exports.validatePlus = {
enable: true,
package: 'egg-validate-plus',
};

13.2 配置插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// config/config.{env}.js

config.validatePlus = {

resolveError(ctx, errors) {

if (errors.length) {

ctx.type = 'json';

ctx.status = 400;

ctx.body = {
code: 400,
error: errors,
message: '参数错误',
};
}
}
};

13.3 使用插件

13.3.1 传入字符串

1
2
// app/controller/xx.js
const { query } = this.ctx.request;

拿到验证结果

1
const validateResult = await this.ctx.validate('user.login', query)

验证不通过时,阻止后面的代码执行

1
if (!validateResult) return

> 注意:不要带上 rules

13.3.2 直接传入验证规则对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// app/controller/xx.js

// 直接引入 rules 文件下的验证规则,也可以是自己写的验证规则对象

const rule = this.app.rules.user.login

// 数据格式

// const rule = {

// id: [

// { required: true },

// { type: 'number', message: 'id 必须为数字 }

// ],

// password: [

// { required: true },

// { type: 'string', message: 'password 必须为字符串 }

// ]

// }



// 从客户端传入的参数

const { query } = this.ctx.request;

// 数据格式:
// query = {

// username: 123456,

// password: 'abcdefg'

// }

// 拿到验证结果

const validateResult = await this.ctx.validate(rule, query)

// 验证不通过时,阻止后面的代码执行

if (!validateResult) return

14 连接redis

Redis client(support redis portocal) based on ioredis for egg framework

14.1 安装

1
npm i egg-redis --save

14.2 配置

Change ${app_root}/config/plugin.js to enable redis plugin:

1
2
3
4
5
6
7
exports.redis = {

enable: true,

package: 'egg-redis',

};

Configure redis information in ${app_root}/config/config.default.js:
Single Client

1
2
3
4
5
6
7
8
config.redis = {
client: {
port: 6379, // Redis port
host: '127.0.0.1', // Redis host
password: 'auth',
db: 0,
}
}

14.3 使用方法

14.3.1 service

app/service/redis.js if(this.app.redis)判断是否有启用redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'use strict';

const Service = require('egg').Service;

class RedisService extends Service {
async set(key, value, seconds) {
value = JSON.stringify(value);
if (this.app.redis) {
if (!seconds) {
await this.app.redis.set(key, value);
} else {
await this.app.redis.set(key, value, 'EX', seconds);
}
}
}

async get(key) {
if (this.app.redis) {
const data = await this.app.redis.get(key);
if (!data) return;
return JSON.parse(data);
}
}
}

module.exports = RedisService;

14.3.2 controller

app/controller/default/index.js如果没有设置redis缓存,就去请求数据,再设置缓存

1
2
3
4
5
6
7
var topNav = await this.ctx.service.cache.get('index_topNav');
if (!topNav) {
topNav = await this.ctx.model.Nav.find({
"position": 1
});
await this.ctx.service.cache.set('index_topNav', topNav, 60 * 60);
}